package cn.edu.pku.ss.situationawareperceiver;

import java.util.ArrayList;
import java.util.Date;
import java.util.Random;

import android.os.Message;
import android.util.Log;
import cn.edu.pku.ss.situationawareperceiver.ContextConstants.ContextType;
import cn.edu.pku.ss.situationawareperceiver.dbrecords.ContextRequestRecord;
import cn.edu.pku.ss.situationawareperceiver.dbrecords.ContextRequestRecord.RetrieveType;
import cn.edu.pku.ss.situationawareperceiver.dbrecords.ContextShareRecord;

public class ContextShareManager {
	/**
	 * Module: Context Sharing Manager (Serve as the core of the Context sharing
	 * mechanism) Responsibilities: 1) Express interest -> When local sensor
	 * can't satisfy the context request, it will transfer the request to this
	 * module. Therefore this module should take the context request and send it
	 * to all the peers. 2) Respond to interest -> Check whether the needed
	 * context is existed. If true then check the privacy level and send it back
	 * to the requester peer. Otherwise it will check it has the sensory
	 * capability to generate this kind of context, or ignore this request.
	 * (Above two methods are done by sending instructions to AllJoynService) 3)
	 * Check local sensory capability with specific context type -> by interact
	 * with the Sensor Manager.
	 */
	private static final String TAG = "ContextShareManager";
	/**
	 * Singleton
	 */
	private static final ContextShareManager mContextShareManager = new ContextShareManager(); 
	
	private ContextShareManager(){
		
	}
	
	public static ContextShareManager getInstance(){
		return mContextShareManager;
	}
	
	/* Handler used to make calls to AllJoyn methods. */
	private static BusHandler mBusHandler;
	public static void setBusHandler(BusHandler iBusHandler){
		mBusHandler = iBusHandler;
	}
	
	private static MainActivity mMainActivity;
	public static void setMainActivity(MainActivity mainActivity){
		mMainActivity = mainActivity;
	}
	
	/**
	 * Temporary store for local context data.
	 */
	private static ArrayList<ContextData> localContexts = new  ArrayList<ContextData>();
	
	public void queryContext(ContextType contextType) {
		/*
		 * For ContextInterpreter (SRA drives) query context. First check if
		 * there is a valid context (ContextMessage not expired ). If there is
		 * not, query SensorManager for local sensors, If local sensors can't
		 * offer this type of context, then build a ContextSharingRequest and
		 * broadcast to all the peers over the opportunistic network as a way of
		 * EXPRESSING INTEREST.
		 */
		ContextData context = null;
		ContextRequestRecord crRec;
		DBAdapter dbAdapter = mMainActivity.getDBAdapter();
		ContextType cTypes[] = ContextType.values();
		/**
		 * Phase 1. Check existing valid context.
		 */
		context = checkLocalContextData(contextType);
		
		if(context!=null){
			logInfo("CSM.queryContext(): Got [" + cTypes[context.contextTypeInt].name() + "] from local contexts.");
			ContextInterpreter.interpretContextToSituation(cTypes[context.contextTypeInt], context.truthValue);
			// db
			if(null != dbAdapter){
				logInfo("[DBINSERT]: inserting a ContextRequestRecord.");
				dbAdapter.insertContextRequestRecord(new ContextRequestRecord(mBusHandler.getMyPeerId(), contextType.ordinal(), RetrieveType.FROM_EXISTING_CONTEXTS.ordinal(), mMainActivity.getUnifiedDateFormat().format(new Date())));
			}
			
			return;
		}
		
		/**
		 * Phase 2. Query SensorManager.
		 */
		
		context = querySensorManagerForContextData(contextType);
		if(context!=null){
			logInfo("CSM.queryContext(): Got [" + cTypes[context.contextTypeInt].name() + "] from local SensorManager.");
			ContextInterpreter.interpretContextToSituation(cTypes[context.contextTypeInt], context.truthValue);
			// db
			if(null != dbAdapter){
				logInfo("[DBINSERT]: inserting a ContextRequestRecord.");
				/********* This should be deleted !!********************/
				boolean shareProbability[] = {false, false, true,false};
				Random rand = new Random();
				if(shareProbability[rand.nextInt(4)]){
					dbAdapter.insertContextRequestRecord(new ContextRequestRecord(mBusHandler.getMyPeerId(), contextType.ordinal(), RetrieveType.FROM_EXISTING_CONTEXTS.ordinal(), mMainActivity.getUnifiedDateFormat().format(new Date())));
				}else
				/**************** End **********************************/
				dbAdapter.insertContextRequestRecord(new ContextRequestRecord(mBusHandler.getMyPeerId(), contextType.ordinal(), RetrieveType.FROM_LOCAL_SENSORS.ordinal() , mMainActivity.getUnifiedDateFormat().format(new Date())));
			}
			return;
		}
		
		/**
		 * Phase 3. Build a ContextShareRequest and send it to all peers on the session. use another method in this class.
		 */
		/* Make sure bus handler is assigned.*/
		if(null == mBusHandler){
			Log.e(TAG, "Error: ContextShareManager.mBusHandler==null!!  ExpressInterest Fail!");
			return;
		}
		// db
		if(null != dbAdapter){
			logInfo("[DBINSERT]: inserting a ContextRequestRecord.");
			dbAdapter.insertContextRequestRecord(new ContextRequestRecord(mBusHandler.getMyPeerId(), contextType.ordinal(), RetrieveType.FROM_PROXIMITY.ordinal(), mMainActivity.getUnifiedDateFormat().format(new Date())));
		}
		Message msg = mBusHandler.obtainMessage(
				BusHandler.SEND_SHARE_REQUEST, buildSharingRequest(contextType));
		mBusHandler.sendMessage(msg);
		logInfo("Send a ContextShareRequest of contextType:" + contextType.name() + " over the proximity.");
		return;
		

	}
	
	public void handleSharedContext(ContextShareMessage shareMsg){
		ContextData newContext = null;
		/* Check redundancy */
		boolean hasRedundancy = false;
		ContextType cTypes[] = ContextType.values();
		for(ContextData lcData: localContexts){
			Date expiredDate = new Date(lcData.expireDate * 1000);  
			if(new Date().after(expiredDate)){
				//invalid -> delete and discard it
				localContexts.remove(lcData);
				logInfo("Founded ContextData ("+ cTypes[lcData.contextTypeInt].name() +") is expired. Deleted it.");
				continue;
			}else{
				if(lcData.contextTypeInt == shareMsg.contextTypeInt){
					logInfo("Found redundancy. Ignore the new ContextShareMsg");
					hasRedundancy = true;
				}
			}
		}
		
		if(!hasRedundancy){
			newContext = new ContextData();
			newContext.initialize(cTypes[shareMsg.contextTypeInt], shareMsg.truthValue	, shareMsg.genDate, shareMsg.expireDate);
			/* Save to local contexts */
			localContexts.add(newContext);
			logInfo("New Context of [" + cTypes[newContext.contextTypeInt].name() + " - " + newContext.truthValue +"] has been added(locally).");
		}else{
			return;
		}
		
		logInfo("Send the new Context of [" + cTypes[shareMsg.contextTypeInt].name() +"] to interpreter.");
		/* Send to Interpreter ( -> SRA)*/
		ContextInterpreter.interpretContextToSituation(cTypes[newContext.contextTypeInt], newContext.truthValue);
		
		/* Save to Database */
		ContextShareRecord csRec = new ContextShareRecord(shareMsg.recipientPeerID, shareMsg.senderPeerId, mMainActivity.getUnifiedDateFormat().format(new Date(shareMsg.genDate * 1000)), shareMsg.contextTypeInt, shareMsg.truthValue);
		DBAdapter dbAdapter = mMainActivity.getDBAdapter();
		if(null != dbAdapter){
			logInfo("[DBINSERT]: inserting a ContextShareRecord.");
			dbAdapter.insertContextShareRecord(csRec);
		}
		
		
	}
	
	public void receiveContextShareRequest(ContextShareRequest csRequest){
		logInfo("ContextShareManager received a context share request from" + csRequest.senderPeerId);
		ContextType cTypes[] = ContextType.values();
		ContextType cType = cTypes[csRequest.contextTypeInt];
		ContextData contextData = null; 
		/* check local contexts */
		contextData = checkLocalContextData(cType);
		if(contextData!=null){
			logInfo("Found a existing local context can answer to the request, now send it back.");
			/* build a context share msg*/
			ContextShareMessage csMsg = buildShareMessage(contextData, mBusHandler.getMyPeerId(), csRequest.senderPeerId);
			/* use bus method to send it back */
			Message msg = mBusHandler.obtainMessage(BusHandler.SEND_SHARE_MESSAGE, csMsg);
			mBusHandler.sendMessage(msg);
			return;
		}
		
		/* decide the willingness to answer it*/
		boolean isWillingToQueryForShare = false;	
		boolean shareProbability[] = {true, false, true,true,true,true};
		Random rand = new Random();
		isWillingToQueryForShare = shareProbability[rand.nextInt(6)];
		logInfo("isWillingToQueryForShare = " + isWillingToQueryForShare);
		
		if(isWillingToQueryForShare){
			logInfo("Willing to Query sensors for share.");
			contextData = querySensorManagerForContextData(cType);
			if(null == contextData){
				logInfo("Can't find availble sensors. Fail to share.");
			}else{
				logInfo("Query finished. Now share the query result to the context requester.");
				/* build a context share msg*/
				ContextShareMessage csMsg = buildShareMessage(contextData, mBusHandler.getMyPeerId(), csRequest.senderPeerId);
				/* use bus method to send it back*/
				Message msg = mBusHandler.obtainMessage(BusHandler.SEND_SHARE_MESSAGE, csMsg);
				mBusHandler.sendMessage(msg);
				/* Send it to local interpreter */
				ContextInterpreter.interpretContextToSituation(cTypes[contextData.contextTypeInt], contextData.truthValue);
				return;
			}
		}else{
			logInfo("Unwilling to Query sensors for share. DISCARD.");
		}
		
	}

	public ContextData checkLocalContextData(ContextType contextType){
		ContextData context = null;

		for(ContextData iContext: localContexts){
			if(contextType.ordinal() == iContext.contextTypeInt){
				logInfo("Find an available ContextData. Now checking its expiry.");
				//Examine expired contexts.
				//transmit from millisecond to second
				Date expiredDate = new Date(iContext.expireDate * 1000);  
				if(new Date().after(expiredDate)){
					//invalid -> delete and discard it
					localContexts.remove(iContext);
					logInfo("Founded ContextData is expired.");
					continue;
				}else{
					//valid
					logInfo("Founded ContextData is valid.");
					context = iContext;
				}
			}
		}
		return context;
	}
	
	public ContextData querySensorManagerForContextData(ContextType contextType){
		
		ContextData context = null;
		SensorManager iSensorManager = SensorManager.getInstance();
		logInfo("Now querying sensors for " + contextType.name());
		/*Check availability first*/
		if(iSensorManager.checkAvailability(contextType)){
			logInfo("Found local sensor can satisfy the query on :" + contextType.name());
			context = iSensorManager.querySensor(contextType);
			if(context!=null){
				logInfo("Got query result from SensorManager. (" + context.truthValue + ")");
				return context;
			}else{
				logInfo("**ERROR**, query SensorManager returns null.");
			}
		}else{
			logInfo("Can't find local sensor for : " + contextType.name());
		}
		return null;
	}
	
//	public static void receiveContextMessage(ContextConstants contextMsg) {
		/*
		 * After AllJoynService module receive a ContextMessage (check recipient
		 * of course), its handler will deliver the content of the message
		 * (ContextMessage here) to this method.
		 */
		
//	}

	private static ContextShareRequest buildSharingRequest(
			ContextType contextType) {
		/*
		 * For local method queryContext() use, to build a context sharing
		 * request for it.
		 */
		//get time
		java.util.Date dt = new Date();
		long lSysTime1 = dt.getTime() / 1000;   //get seconds
		// Mock
		ContextShareRequest request = new ContextShareRequest();
		request.initialize(mBusHandler.getMyPeerId(),
				contextType.ordinal(),
				lSysTime1, 30);	//expired after 30 seconds.

		return request;
	}
	
	private static ContextShareMessage buildShareMessage(ContextData contextData, String senderPeerId, String recipientPeerID){
		ContextShareMessage csMessage = new ContextShareMessage();
		csMessage.initialize(contextData.contextTypeInt, contextData.truthValue, contextData.genDate, contextData.expireDate, senderPeerId, recipientPeerID);
		return csMessage;
	}
	
	public static void clearLocalContexts(){
		localContexts.clear();
	}
	
	/* Log methods */
	public static void logInfo(String msg) {
		Log.i(TAG, msg);
	}
}
